package com.llb666.common.core.utils.id.snow;

/**
 * ID生成器工具类 -- 雪花算法
 *
 * @author llb666
 */
//@Configuration
public class SnowFlakeUtil {

    /*
     * workerId ---> 终端id
     * dataCenterId--->数据中心id
     *
     * @return Snowflake
     */
//    @Bean
//    public Snowflake snowflake() {
//        return new Snowflake(11L, 8L);
//    }

    /**
     * 数据中心/机房标识所占bit位数
     */
    private final static long DATACENTER_BIT = 5;
    /**
     * 机器标识所占bit位数
     */
    private final static long WORKER_ID_BIT = 5;
    /**
     * 每毫秒下的序列号所占bit位数,2的12次方,0到4095,一秒钟最多生产4096个数字
     */
    private final static long SEQUENCE_BIT = 12;

    // 起始时间戳 2023-07-14 00:00:00,可以根据自己的需求进行修改
    private final static long EPOCH = 1689264000000L;
    // 机器标志相对序列号的偏移量 12位
    private final static long WORKER_ID_LEFT = SEQUENCE_BIT;
    // 机房标志相对机器的偏移量 17 = 12 + 5位
    private final static long DATA_CENTER_LEFT = WORKER_ID_LEFT + WORKER_ID_BIT;
    // 时间戳标志相对机器的偏移量 22 = 17 + 5位
    private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATACENTER_BIT;

    // 用位运算计算出最大支持的数据中心编号 31
    private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATACENTER_BIT);
    // 用位运算计算出最大支持的机器编号 31
    private final static long MAX_WORKER_ID_NUM = ~(-1L << WORKER_ID_BIT);
    // 用位运算计算出12位能存储的最大整数,12位的情况下为4095
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
    /**
     * 机房 机器 序列号 上一次请求保存的时间戳
     */
    private static long dataCenterId;
    private static long workerIdId;
    private static long sequence = 0L;// 自增序列号(相当于计数器)
    private static long lastStamp = -1L;

    public SnowFlakeUtil() {
    }

    /**
     * 构造函数
     *
     * @param workerId     机器ID (0~31)
     * @param dataCenterId 数据中心ID (0~31)
     */
    public SnowFlakeUtil(long workerId, long dataCenterId) {
        if (workerId > MAX_WORKER_ID_NUM || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID_NUM));
        }
        if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATA_CENTER_NUM));
        }
        SnowFlakeUtil.workerIdId = workerId;
        SnowFlakeUtil.dataCenterId = dataCenterId;
    }

    /**
     * 此属性用于限定一个随机上限，在不同毫秒下生成序号时，给定一个随机数，避免偶数问题。
     * 注意次数必须小于{@link #MAX_SEQUENCE}
     * 不同毫秒,序列号取一个[0,randomSequenceLimit)之间的随机数,避免都是偶数的情况
     */

//    private final long randomSequenceLimit = 2;

    //最大抖动上限值，最好设置为奇数,注意该值必须小于等于MAX_SEQUENCE即4095
    private static int maxVibrationOffset = 1;
    //跨毫秒时的序列号,跨毫秒获取时该序列号+1
    private static volatile int sequenceOffset = -1;

    /**
     * 产出下一个Id
     */
    public static synchronized long nextId() {
        // 获取当前的时间戳
        long curStamp = getCurrentStamp();
        // 若当前时间戳 < 上次时间戳则抛出异常
        if (curStamp < lastStamp) {
            throw new RuntimeException("Clock moved backwords. Refusing to generate id");
        }
        // 1.同一毫秒内
        if (curStamp == lastStamp) {
            // 1.1 相同毫秒内 id自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 1.2 同一毫秒内 序列数已经达到最大4095,等待下一个毫秒到来在生成
            if (sequence == 0L) {
                // 获取下一秒的时间戳并赋值给当前时间戳
                curStamp = getNextMill();
            }
        } else {
            // 2.不同毫秒,序列号重置为0
//            sequence = 0L;
            // 不同毫秒,序列号取一个[0,randomSequenceLimit)之间的随机数,避免都是偶数的情况
//            sequence = ThreadLocalRandom.current().nextLong(randomSequenceLimit);
            // 2.不同毫秒,处理抖动上限,超过了抖动上限则将sequenceOffset计数器归0,否则sequenceOffset累加1
            // 将sequence设置为sequenceOffset
            vibrateSequenceOffset();
            sequence = sequenceOffset;
        }
        // 3.当前时间戳存档, 用于下次生成id对比是否为同一毫秒内
        lastStamp = curStamp;
        // 4.或运算拼接返回id
        return (curStamp - EPOCH) << TIMESTAMP_LEFT // 时间戳部分
                | dataCenterId << DATA_CENTER_LEFT // 机房部分
                | workerIdId << WORKER_ID_LEFT // 机器部分
                | sequence; // 序列号部分
    }

    private static long getNextMill() {
        long mill = getCurrentStamp();
        // 循环获取当前时间戳, 直到拿到下一秒的时间戳
        while (mill <= lastStamp) {
            mill = getCurrentStamp();
        }
        return mill;
    }

    private static long getCurrentStamp() {
        return System.currentTimeMillis();
    }

    private static void vibrateSequenceOffset() {
        //不同毫秒时间,处理抖动上限,超过了抖动上限则将sequenceOffset计数器归0,否则sequenceOffset累加1
        sequenceOffset = sequenceOffset >= maxVibrationOffset ? 0 : sequenceOffset + 1;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1L);
            System.out.println(SnowFlakeUtil.nextId());
        }
    }
}
